#include <GL/glut.h>
#include <cubECamera.hh>

namespace CubE
{

   //
   Camera::Camera(CameraType type)
      :_mType(type),
       _mPosition(Vector3::ZERO),
       _mLookat(Vector3::UNIT_Z*(-1)),
       _mUpDirect(Vector3::UNIT_Y),
       _mFocalLength(1.0f),
       _mNearClipDist(0),
       _mFarClipDist(100)
   {
      
   }
   
   Camera::Camera(CameraType type, const Vector3& pos, const Vector3& lookat,
		  float near_dist, float far_dist)
      :_mType(type),
       _mPosition(pos),
       _mLookat(lookat),
       _mUpDirect(Vector3::UNIT_Y),
       _mNearClipDist(near_dist),
       _mFarClipDist(far_dist)
   {
      Vector3 direction = lookat - pos ;
      _mFocalLength = direction.GetLength();
      //'cause we're using openGL system(right-hand system)
      _mOrientation = Quaternion(Vector3::UNIT_Z*(-1), direction);
   }
   
   Camera::Camera(CameraType type, float pos_x, float pos_y, float pos_z,
		  float lookat_x, float lookat_y, float lookat_z,
		  float near_dist, float far_dist)
            :_mType(type),
             _mPosition(Vector3(pos_x, pos_y, pos_z)),
             _mLookat(Vector3(lookat_x, lookat_y, lookat_z)),
             _mUpDirect(Vector3::UNIT_Y),
             _mNearClipDist(near_dist),
             _mFarClipDist(far_dist)

   {
      _mFocalLength = (_mLookat - _mPosition).GetLength();
      
      Vector3 direction( lookat_x - pos_x,
                         lookat_y - pos_y,
                         lookat_z - pos_z );
      
      //'cause we're using openGL system(right-hand system)
      _mOrientation = Quaternion(Vector3::UNIT_Z*(-1), direction);
   }

   Camera::~Camera(void)
   {
      
   }

   //move & rotate , at camera view space coordinates
   void Camera::Move(float vec_x, float vec_y, float vec_z)
   {
      Move(Vector3(vec_x, vec_y, vec_z));
   }
   
   void Camera::Move(const Vector3& vec)
   {
      Vector3 vecAtLocal = vec.Rotate(_mOrientation);
      _mPosition += vecAtLocal;
      _mLookat += vecAtLocal;
   }

   void Camera::Rotate(const Vector3& axis, const Degree& angle)
   {
      Radian r(angle);
      Rotate(axis, r);
   }

   void Camera::Rotate(const Vector3& axis, const Radian& angle)
   {
      //this guarantees that the fresh generated Quaterrnion is
      //normalized
      Vector3 vec = axis;
      if(Vector3::UNIT_Y != axis) {
         vec = axis.Rotate(_mOrientation);
      }
         
      Quaternion q = Quaternion(vec, angle);
      q.Normalize();

      //Note the multiply order. The first applied should be
      //multiplied last.
      _mOrientation = q * _mOrientation;
      _mOrientation.Normalize();

      Vector3 frontDirect = GetFrontDirectionUnit();
      frontDirect = frontDirect * _mFocalLength;
      Vector3 rightDirect = frontDirect.CrossProduct(Vector3::UNIT_Y);
      Vector3 rightDirectLocal = rightDirect.Rotate(_mOrientation.GetConjugate());
      if( rightDirectLocal.Get(Vector3::_X) < 0.0f ) {
         _mUpDirect = Vector3::UNIT_Y*(-1);
      }
      else {
         _mUpDirect = Vector3::UNIT_Y;
      }

      if(FREE_CAM == _mType) {
         _mLookat = _mPosition + frontDirect;
      }
      else if(TARGET_CAM == _mType) {
         _mPosition = _mLookat - frontDirect;
      }

   }


   Vector3 Camera::GetFrontDirectionUnit(void) const
   {
      Vector3 localDir = Vector3::UNIT_Z*(-1);
      Vector3 direct = localDir.Rotate(_mOrientation);
      direct.Normalize();
      return direct;
   }

   void Camera::GetPosition(float& pos_x, float& pos_y, float& pos_z) const
   {
      pos_x = _mPosition[0];
      pos_y = _mPosition[1];
      pos_z = _mPosition[2];
   }
}
